#ifndef DRAW_H
#define DRAW_H

const double PI = 3.1415926 ;
const unsigned WINDOW_WIDTH = 500;
const unsigned WINDOW_HEIGHT = 600 ; 
const float INFINITY = 1e5 ;

const float gaussian_sigma = 0.84089642f ;
const float gaussian_constant[5] = 
//{0.22508352f,	0.11098164f, 0.01330373f, 0.00038771f, 0.0f};
{0.22508352f,	0.15098164f, 0.08330373f, 0.0338771f, 0.0f};
//{0.3308352f,	0.30098164f, 0.20330373f, 0.0638771f, 0.0f};
//{0.3308352f,	0.30098164f, 0.20330373f, 0.1638771f, 0.08f};



float randreal() ;


//void prepare_color(std::vector<Face *> & faces); 
struct int_RGB
{
	int_RGB (short _r, short _g, short _b): r(_r), g(_g), b(_b), a(255) {}
	int_RGB (short _r, short _g, short _b, short _a): r(_r), g(_g), b(_b), a(_a) {}
	int_RGB():r(-1),g(-1),b(-1), a(255) {}
	
	
	short r, g, b, a;
	friend inline int_RGB operator * (const int_RGB & color, float scale)
	{
		return int_RGB(short(color.r * scale), short(color.g * scale), short(color.b * scale)) ;
	}
	friend inline int_RGB operator + (const int_RGB & lhs, const int_RGB & rhs)
	{
		return int_RGB(lhs.r + rhs.r, lhs.g + rhs.g, lhs.b + rhs.b);
	}
	friend inline bool operator == (const int_RGB & lhs, const int_RGB & rhs)
	{
		return (lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && lhs.a == rhs.a);
	}
	friend int_RGB  max_blend (const int_RGB & lhs, const int_RGB & rhs)
	{
		return int_RGB ( max (lhs.r, rhs.r), max (lhs.g, rhs.g), max (lhs.b, rhs.b));
	}
	friend int_RGB  min_blend (const int_RGB & lhs, const int_RGB & rhs)
	{
		return int_RGB ( min (lhs.r, rhs.r), min (lhs.g, rhs.g), min (lhs.b, rhs.b));
	}
	friend const int_RGB solid_color (const int_RGB & c)
	{
		float alpha = float (c.a )/255 ;
		return int_RGB (c.r * alpha, c.g * alpha, c.b * alpha);
	}
	friend int_RGB  linear_blend (const int_RGB & front_color, const int_RGB & back_color)
	{
		if (front_color == int_RGB () && back_color == int_RGB())
		{
			return int_RGB ();
		}
		if (front_color == int_RGB ())
		{
			return back_color ;
		}
		if (back_color == int_RGB ())
		{
			return front_color ;
		}
		int_RGB color = front_color * 0.5 + back_color * 0.5f ;
		color.a = ( front_color.a + back_color.a ) * 0.5f; 
		return color ;
	}
	friend int_RGB  alpha_blend (const int_RGB & front_color, const int_RGB & back_color)
	{
		if (front_color == int_RGB () && back_color == int_RGB())
		{
			return int_RGB ();
		}
		if (front_color == int_RGB ())
		{
			return back_color ;
		}
		if (back_color == int_RGB ())
		{
			return front_color ;
		}
		int_RGB color  ;
		float f_alpha = float(front_color.a) / 255.0f ;
		float b_alpha = float(back_color.a) / 255.0f  ;

		color  = front_color * f_alpha + back_color *( b_alpha * (1-f_alpha)) ;
		color.a = (f_alpha + (1-f_alpha) * b_alpha ) * 255;
		return color ;
	}
};

inline float linear_gaussian_blur(float distance_ratio) /*[0,1] from the center*/
{
	int a = int(distance_ratio * 4);
	int b = a + 1;
	if (b >= 5) b = a;
	float weight_a = distance_ratio * 4 - a;
	float weight_b = 1.0f - weight_a;
	return ( gaussian_constant[a] * weight_b + gaussian_constant[b] * weight_a ) / gaussian_constant[0]  ;
}
struct color_RGB
{
	color_RGB(float _r, float _g, float _b):r(_r),g(_g),b(_b) {}
	color_RGB():r(1.0),g(1.0),b(1.0) {}
	float r, g, b;
};
int_RGB rand_color()  ;

float compute_texture() ;
struct STATES
{
	STATES()
	{
		sample_points = false;
		selected_face_idx = 0; 
	}
	bool sample_points ; /*Controled by key 'S'*/
	unsigned selected_face_idx ;
};
class Point2 {
public:
	Point2(float x_in, float y_in) : x(x_in), y(y_in) { }
	Point2() : x(0), y(0) { }
	float x ;
	float y ;

	inline float length() const
	{
		return sqrt (x*x + y * y);
	}

	friend inline std::ostream& operator<<(std::ostream& out, const Point2& v) 
	{
		return out << v.x << " " << v.y ;
	}

	friend inline std::istream& operator>>(std::istream& in, Point2& v) 
	{
		return in >> v.x >> v.y ;
	}

	friend inline Point2 operator+(const Point2 & v1, const Point2 & v2) 
	{
		return Point2( v1.x + v2.x, v1.y + v2.y) ;
	}

	friend inline Point2 operator-(const Point2 & v1, const Point2 & v2) 
	{
		return Point2(v1.x - v2.x, v1.y - v2.y) ;
	}

	friend inline Point2 operator * (const Point2 & lhs, float rhs)
	{
		return Point2 (lhs.x * rhs, lhs.y * rhs);
	}
	friend inline float operator ^ (const Point2 & lhs, const Point2 & rhs) 
	{
		return (lhs.x * rhs.y - lhs.y * rhs.x );
	}
	friend inline float operator * (const Point2 & lhs, const Point2 & rhs) 
	{
		return (lhs.x * rhs.x + lhs.y * rhs.y );
	}
} ;
class Point3 
{
public:
	Point3 () {}
	Point3 (float a, float b, float c)
	{
		x = a; y = b; z = c;
	}
	Point3 (Point2 & p2)
	{
		x = p2.x ;
		y = p2.y ; 
		z = 0.0f ;
	}
	float x, y, z;
	inline float length() const
	{
		return sqrt (x*x + y * y + z*z);
	}
	inline Point3 & normalize() 
	{
		float len = length() ;
		x /= len; 
		y /= len;
		z /= len;
		return (*this);
	}

	friend inline float operator == (const Point3 & lhs, const Point3 & rhs)  
	{
		return (lhs.x == rhs.x  && lhs.y == rhs.y && lhs.z == rhs.z);
	}
	friend inline float operator * (const Point3 & lhs, const Point3 & rhs)
	{
		return (lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z);
	}
	friend inline Point3 operator + (const Point3 & lhs, const Point3 & rhs)
	{
		return Point3 (lhs.x + rhs.x , lhs.y + rhs.y , lhs.z + rhs.z);
	}
	friend inline Point3 operator / (const Point3 & lhs, float rhs)
	{
		return Point3 (lhs.x / rhs, lhs.y / rhs, lhs.z / rhs);
	}
	friend inline Point3 operator * (const Point3 & lhs, float rhs)
	{
		return Point3 (lhs.x * rhs, lhs.y * rhs, lhs.z * rhs);
	}
	friend inline Point3 operator - (const Point3 & lhs, const Point3 & rhs)
	{
		return Point3 (lhs.x - rhs.x , lhs.y - rhs.y , lhs.z - rhs.z);
	}
	friend inline Point3 operator ^ (const Point3 & lhs, const Point3 & rhs) 
	{
		return Point3 (lhs.y * rhs.z - lhs.z * rhs.y, 
			(lhs.z * rhs.x - lhs.x * rhs.z),
			lhs.x * rhs.y - lhs.y * rhs.x );
	}
	friend inline std::ostream& operator<<(std::ostream& out, const Point3 & v) 
	{
		return out << v.x << " " << v.y << " " << v.z ;
	}
	friend inline std::istream& operator>>(std::istream& in, Point3 & v) 
	{
		return in >> v.x >> v.y >> v.z ;
	}
};

typedef Point3 Point ;
typedef Point3 Vector ;
typedef Point3 Vector3 ;



typedef Point2 Vector2 ;


class Resolution 
{
public:
	Resolution():reso(1) {}


private:
	unsigned reso ; 
	//std::vector<std::vector<color_RGB> > color_array; 
};

inline int barycentric2index (unsigned res,  unsigned i, unsigned j)
{
	return  (res + 1 + res + 2 - i ) * i / 2 + j; 
}
void index2barycentric (unsigned res, unsigned idx, unsigned & i, unsigned & j);

inline float det3 ( float m[3][3])
{
	float det = m[0][0] *(m[2][2]*m[1][1] - m[2][1]* m[1][2]) 
			- m[1][0] * (m[2][2]*m[0][1] - m[2][1]* m[0][2])
			+ m[2][0] * (m[1][2]*m[0][1] - m[1][1]* m[0][2]);
	return det ;
}
inline void inverse3 (float m[3][3],  float invm[3][3])
{
	float rdet = 1.0f / det3(m);
	invm[0][0] =   rdet * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) ;
	invm[0][1] = - rdet * (m[0][1] * m[2][2] - m[0][2] * m[2][1]) ;
	invm[0][2] =   rdet * (m[0][1] * m[1][2] - m[0][2] * m[1][1]) ;

	invm[1][0] = - rdet * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) ;
	invm[1][1] =   rdet * (m[0][0] * m[2][2] - m[0][2] * m[2][0]) ;
	invm[1][2] = - rdet * (m[0][0] * m[1][2] - m[0][2] * m[1][0]) ;

	invm[2][0] =   rdet * (m[1][0] * m[2][1] - m[1][1] * m[2][0]) ;
	invm[2][1] = - rdet * (m[0][0] * m[2][1] - m[0][1] * m[2][0]) ;
	invm[2][2] =   rdet * (m[0][0] * m[1][1] - m[0][1] * m[1][0]) ;
}
inline Vector3 multiply_vector(float m[3][3], Vector3 & v)
{
	Vector3 result;
	result.x = m[0][0] * v.x + m[0][1] * v.y + m[0][2] * v.z;
	result.y = m[1][0] * v.x + m[1][1] * v.y + m[1][2] * v.z;
	result.z = m[2][0] * v.x + m[2][1] * v.y + m[2][2] * v.z;
	return result;
}

/////

//const float DEG2RAD = 3.141593f / 180;
const float FOV_Y = 60.0f;
//Initial values to set frustrum
const float INIT_LEFT = -1.0f;
const float INIT_RIGHT = 1.0f;
const float INIT_BOTTOM = -1.0f;
const float INIT_TOP = 1.0f;
const float INIT_NEAR = 0.0001f;
const float INIT_FAR = 100.0f;
//Initial values to set camera
const float CAMERA_AGL_X = 45.0f;     // pitch 
const float CAMERA_AGL_Y = -45.0f;    // heading 
const float CAMERA_DIST = -3.5f;    


enum ViewMode {EDIT, VIEW};
enum LeftButtonFunction {TRANSLATE, ROTATE, PAINT} ;

class ViewMatrix 
{
public:
	ViewMatrix () {}
	ViewMatrix (ViewMatrix & vm)
	{
		for (int i = 0; i < 16; ++i )
		{
			this->modelViewMatrix[i] = vm.modelViewMatrix[i];
			this->projectionMatrix[i] = vm.projectionMatrix[i];
			
		}
		for (int i = 0; i < 4; ++i )
		{
			this->viewPort [i] = vm.viewPort[i];
		}
	}
	void getCoordEnv()
	{
		glGetDoublev (GL_MODELVIEW_MATRIX, this->modelViewMatrix);
		glGetDoublev (GL_PROJECTION_MATRIX, this->projectionMatrix);
		glGetIntegerv(GL_VIEWPORT, this->viewPort);	
	}
	double modelViewMatrix[16];
	double projectionMatrix[16];  
	int viewPort[4];

	inline Vector3 mappingObj2Win (const Vector3 & objCoord)
	{
		double winx, winy, winz ;
		gluProject(objCoord.x, objCoord.y, objCoord.z, modelViewMatrix, projectionMatrix, viewPort,
			& winx, & winy, & winz );
		winy = WINDOW_HEIGHT - winy ;
		return Vector3 (float(winx), float(winy), float(winz));
	}

	inline Vector3 mappingWin2Obj (const Vector3 & winPos)
	{
		double x, y, z;
		gluUnProject(winPos.x, WINDOW_HEIGHT - winPos.y, winPos.z, modelViewMatrix, projectionMatrix, viewPort, 
			& x, &y , &z);
		return Vector3 (float(x), float(y), float(z));
	}

	void pick_ray(const Vector2 & winPos, Point3 & p1, Point3 & p2)
	{
		// p1 is on z-near
		// p2 is on z-far
		p1  = mappingWin2Obj(Vector3 (winPos.x, winPos.y, 0.0f));
		p2  = mappingWin2Obj(Vector3 (winPos.x, winPos.y, 1.0f));
	}
};

class ViewParameter
{
public:
	ViewParameter():mouseLeftDown(false), mouseRightDown(false), windowSizeChanged(true), updated(true),
		cameraAglX (CAMERA_AGL_X), cameraAglY(CAMERA_AGL_Y), cameraDis (CAMERA_DIST) , 
		projectionLeft(INIT_LEFT), projectionRight(INIT_RIGHT), 
		projectionBottom(INIT_BOTTOM) , projectionTop(INIT_TOP),
		projectionNear (INIT_NEAR), projectionFar(INIT_FAR), cameraX(0.0f), cameraY (0.0f),
		mouseX(0), mouseY(0), viewMode(VIEW), lbf(TRANSLATE){}
public:
	int windowWidth;
	int windowHeight;
	bool windowSizeChanged;

	bool mouseLeftDown;
	bool mouseRightDown;
	int mouseX;
	int mouseY;

	float cameraAglX;
	float cameraAglY;
	float cameraDis ;
	float cameraX ;
	float cameraY ;

	float projectionLeft;
	float projectionRight;
	float projectionBottom;
	float projectionTop;
	float projectionNear;
	float projectionFar;

	ViewMatrix viewMatrix ;

	bool updated;
	ViewMode viewMode; 
	LeftButtonFunction lbf ;
} ;


//This class do not contain content of a mesh. Just reindexing vertex and selecting 
//some face in the original mesh.

#endif
